import sys
import threading
from stockpyle._stacker import Stacker
from stockpyle._deferrals._base import BaseDeferredStore, StorageOperation, ShutdownOperation, SuccessResponse, FailureResponse, ShutdownResponse
try:
    import processing
    __HAS_PROCESSING = True
except ImportError:
    __HAS_PROCESSING = False


if not __HAS_PROCESSING:
    
    # we do not have the processing library, stub out the object with an exception
    # upon instantiation
    class SubprocessDeferredStore(BaseDeferredStore):
        
        def __init__(self, *args, **kwargs):
            raise StandardError("you cannot instantiate a SubprocessDeferredStore without the 'processing' module")

    
else:
    
    # we have the processing library, use it
    class SubprocessDeferredStore(BaseDeferredStore):
    
        def __init__(self, blueprint, store_name, optimistic_stores):
            super(SubprocessDeferredStore, self).__init__(blueprint=blueprint, store_name=store_name, optimistic_stores=optimistic_stores)
            self.__blueprint = blueprint
            self.__store_name = store_name
            
            # subprocess management
            self.__lock = threading.Lock()
            self.__operation_queue = None
            self.__response_queue = None
            self.__operation_eater = None
        
        def __is_running(self):
            return bool(self.__response_queue)
        
        def __ensure_running(self):
            self.__lock.acquire()
            try:
                if not self.__is_running():
                    self.__operation_queue = processing.Queue()
                    self.__response_queue = processing.Queue()
                    def loop(blueprint, store_name, operation_queue, response_queue):
                        store = Stacker(blueprint=blueprint).get_store(store_name)
                        keepgoing = True
                        while keepgoing:
                    
                            # get the operation
                            operation = operation_queue.get()
                    
                            if isinstance(operation, ShutdownOperation):
                                # we are trying to shut down
                                keepgoing = False
                                response_queue.put(ShutdownResponse())

                            else:
                    
                                # perform the operation
                                try:
                                    operation.perform(store=store)
                                    response_queue.put(SuccessResponse(operation=operation))
                                except Exception, e:
                                    response_queue.put(FailureResponse(operation=operation, exception=e))
            
                    self.__operation_eater = processing.Process(target=loop, args=(
                        self.__blueprint,
                        self.__store_name,
                        self.__operation_queue,
                        self.__response_queue,
                        ))
                    self.__operation_eater.start()
            finally:
                self.__lock.release()
    
        def _enqueue_operation(self, operation):
            """schedules an operation to happen asynchronously"""
            self.__ensure_running()
            self.__operation_queue.put(operation)
            
        def _dequeue_response(self):
            """returns the result of the least-recently completed Operation (SuccessResponse/FailureResponse)"""
            self.__ensure_running()
            return self.__response_queue.get()
        
        def _shutdown_queue(self):
                
            # we got a shutdown scheduled, wait for the eater to finish
            if not self.__response_queue.empty():
                # FIXME: use logging
                print >> sys.stderr, "ERROR: queue was not entirely consumed"
            dead_item_counts = {}
            while not self.__response_queue.empty():
                obj = self.__response_queue.get()
                key = obj.__class__.__name__
                if key not in dead_item_counts:
                    dead_item_counts[key] = 1
                else:
                    dead_item_counts[key] += 1
            if len(dead_item_counts):
                # FIXME: use logging
                print >> sys.stderr, "DEAD: %s" % dead_item_counts
            self.__operation_eater.join()
            self.__operation_eater.terminate()
            self.__operation_eater = None
            
            # stop the manager
            self.__response_queue.close()
            self.__operation_queue.close()
            self.__response_queue.jointhread()
            self.__operation_queue.jointhread()
            self.__response_queue.canceljoin()
            self.__operation_queue.canceljoin()
            self.__response_queue = None
            self.__operation_queue = None